package org.scalpel.common;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;

import android.util.Log;

/**
 * 
 * @author Jerome Monceaux
 * @version 1.0
 *
 * HHObject allow developer to define a class that could be instantiate 
 * and linked to other from a configuration file or code. 
 */
public abstract class HHObject extends Observable implements Observer {
  
  public String fName;

  ArrayList<Object[]> fWires = new ArrayList<Object[]>();
  HashMap<Object[], Method> fWireMethod = new HashMap<Object[], Method>();
  
  public abstract void finish();
  
  public HHObject(String fName) {
    super();
    this.fName = fName;
  }
  
  public void setOutput(String name, Object[] o){
    Object[] v = new Object[2];
    v[0] = name;
    v[1] = o;
    setChanged();
    notifyObservers(v);
  }
  
  
  public void setWire(Observable o, String shortOutputName, String shortInputName){
    for( Object[] w : fWires ){
      if( w[0] == shortInputName && w[1] == shortOutputName ) return;
    }
    Object[] w = new Object[2];
    w[0] = shortInputName;
    w[1] = shortOutputName;
    fWires.add(w);
    o.addObserver(this);
    //o.notifyObservers();
  }
  public void removeAllWireFromInput(Observable o, String shortInputName){
    ArrayList<Object[]> toDelete = new ArrayList<Object[]>();
    for( Object[] w : fWires ){
      if( w[0] == shortInputName )
        toDelete.add(w);
    }
    for( Object[] d : toDelete ){
      fWires.remove(d);
      fWireMethod.remove(d);
    }
    
    //o.deleteObserver(this); // TODO : only if there is no wire anymore
    o.notifyObservers();
  }
  public void removeWire(Observable o, String shortOutputName, String shortInputName){
    ArrayList<Object[]> toDelete = new ArrayList<Object[]>();
    for( Object[] w : fWires ){
      if( w[0] == shortInputName && w[1] == shortOutputName )
        toDelete.add(w);
    }
    for( Object[] d : toDelete ){
      fWires.remove(d);
      fWireMethod.remove(d);
    }
    
    // o.deleteObserver(this); // TODO : only if there is no wire anymore
    o.notifyObservers();
  }
  
  @Override
  public void update(Observable o, Object arg){
    String outputName = (String) ((Object[])arg)[0];

    // explore map to find all output
    for( Object[] w : fWires ){
      if( w[1].equals(outputName) )
          setInput(w, (String)w[0], ((Object[])((Object[])arg)[1]));
    }
  }
  
  // setInput signature mainly used by xmlrpc server
  public Object[] setInput(String inputName, ArrayList<Object> params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
    Object[] p = new Object[params.size()];
    for(int i = 0; i < params.size(); i++)
    {
      p[i] = params.get(i);
    }
    Object r = setInput(null, inputName, p);
    if (r == null) return null; 
    return new Object[]{r};
  }
  
  public Object setInput(Object[]wire, String inputName, Object[] args){
    Method mQuick = null;
    if( wire != null)
      mQuick = fWireMethod.get(wire); //great CPU optimization
    if( mQuick != null){
      try {
        return mQuick.invoke(this,args);
      } catch (IllegalArgumentException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      } catch (IllegalAccessException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      } catch (InvocationTargetException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      fWireMethod.remove(wire);
      return null;
    }
    
    // First call is used to fill the fWireMethod map.
    // So, this will not be necessary next time for this wire.
    @SuppressWarnings("rawtypes")
    Class[] paramTypes = null;
    if(args != null)
    {
      paramTypes = new Class[args.length];
      for(int i=0;i<args.length;++i)
      {
        paramTypes[i] = args[i].getClass();
      }
    }
    try {
      Method m;
      @SuppressWarnings("rawtypes")
      Class c = this.getClass();
      m = ReflectionUtil.getCompatibleMethod(c, inputName, paramTypes);
      fWireMethod.put(wire, m);
      return m.invoke(this,args);
    } catch (SecurityException e) {
      Log.e(fName, e.toString());
      e.printStackTrace();
    } catch (IllegalArgumentException e) {
      Log.e(fName, e.toString());
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      Log.e(fName, e.toString());
      e.printStackTrace();
    } catch (InvocationTargetException e) {
      Log.e(fName, e.toString());
      e.printStackTrace();
    }
    fWireMethod.remove(wire);
    return null;
  }

}
